import json
import os
import sys
import time
from difflib import SequenceMatcher
from typing import Any, Dict, List

import networkx as nx


class CallGraphBuilder:
    def __init__(self, project_root: str = None):
        self.graph = nx.DiGraph()
        self.current_func = None
        self.start_time = time.time()
        self.execution_times: Dict[str, float] = {}
        self.return_values: Dict[str, Any] = {}
        self.execution_status: Dict[str, str] = {}
        # 获取项目根目录的绝对路径
        self.project_root = os.path.abspath(
            project_root if project_root else os.getcwd()
        )
        # 记录当前调用栈，用于处理返回事件
        self.call_stack = []
        # 缓存已经判断过的文件路径
        self.path_cache: Dict[str, bool] = {}

    def is_project_file(self, filename: str) -> bool:
        """判断一个文件是否属于项目内部"""
        # 过滤掉动态执行的代码
        if filename == '<string>' or filename == '<stdin>':
            return False

        if filename in self.path_cache:
            return self.path_cache[filename]

        try:
            # 获取文件的绝对路径
            abs_path = os.path.abspath(filename)
            # 检查是否在项目目录下
            is_internal = abs_path.startswith(self.project_root)
            # 排除虚拟环境、第三方库和特殊文件
            is_internal = (
                is_internal
                and not any(
                    x in abs_path
                    for x in [
                        'site-packages',
                        'dist-packages',
                        'venv',
                        '.env',
                        '__pycache__',
                    ]
                )
                and not filename.startswith('<')
            )
            self.path_cache[filename] = is_internal
            return is_internal
        except:
            self.path_cache[filename] = False
            return False

    def get_qualified_name(self, frame) -> str:
        """获取函数的限定名称"""
        module = frame.f_globals.get('__name__', '')
        func_name = frame.f_code.co_name
        class_name = ''

        # 尝试获取类名（如果是类方法）
        if 'self' in frame.f_locals:
            class_name = frame.f_locals['self'].__class__.__name__

        if class_name:
            return f'{module}.{class_name}.{func_name}'
        return f'{module}.{func_name}'

    def trace_calls(self, frame, event, arg):
        if event == 'call':
            filename = frame.f_code.co_filename

            # 检查是否是项目内部文件
            if not self.is_project_file(filename):
                return self.trace_calls

            # 获取完整的函数名称
            func_name = self.get_qualified_name(frame)

            # 过滤掉一些特殊的函数名
            if func_name.startswith('__') or func_name in ['<module>', '<lambda>']:
                return self.trace_calls

            caller = self.current_func

            # 更新调用栈和当前函数
            self.call_stack.append(self.current_func)
            self.current_func = func_name

            # 添加节点
            if func_name not in self.graph:
                # 确保文件路径是有效的
                file_path = filename
                if not filename.startswith('<'):
                    try:
                        file_path = os.path.relpath(filename, self.project_root)
                    except ValueError:
                        file_path = filename

                self.graph.add_node(func_name)
                self.graph.nodes[func_name].update(
                    {
                        'file': file_path,
                        'lineno': frame.f_code.co_firstlineno,
                        'timestamp': time.time() - self.start_time,
                        'module': frame.f_globals.get('__name__', ''),
                        'source': frame.f_code.co_name,  # 添加源代码名称
                    }
                )
                self.execution_status[func_name] = 'started'

            # 添加边
            if caller:
                self.graph.add_edge(caller, func_name)

        elif event == 'return':
            filename = frame.f_code.co_filename

            # 检查是否是项目内部文件
            if not self.is_project_file(filename):
                return self.trace_calls

            if self.current_func:
                self.execution_times[self.current_func] = time.time() - self.start_time
                # 安全地处理返回值
                try:
                    return_str = str(arg) if arg is not None else None
                    # 限制返回值字符串的长度
                    if return_str and len(return_str) > 1000:
                        return_str = return_str[:1000] + '...'
                    self.return_values[self.current_func] = return_str
                except:
                    self.return_values[self.current_func] = (
                        '<<value cannot be serialized>>'
                    )

                self.execution_status[self.current_func] = 'completed'
                # 从调用栈中恢复上一个函数
                self.current_func = self.call_stack.pop() if self.call_stack else None

        return self.trace_calls

    def start_trace(self):
        sys.settrace(self.trace_calls)

    def stop_trace(self):
        sys.settrace(None)

    def save_graph(self, filename: str):
        """保存图模型到文件"""
        # 将图转换为字典格式
        graph_data = nx.node_link_data(self.graph)
        # 添加额外的执行信息
        graph_data['execution_times'] = self.execution_times
        graph_data['execution_status'] = self.execution_status
        graph_data['return_values'] = self.return_values
        graph_data['project_root'] = self.project_root

        # 保存为JSON文件
        with open(filename, 'w') as f:
            json.dump(graph_data, f, indent=2, default=str)

    def load_graph(self, filename: str):
        """从文件加载图模型"""
        with open(filename, 'r') as f:
            graph_data = json.load(f)

        # 恢复执行信息
        self.execution_times = graph_data.pop('execution_times', {})
        self.execution_status = graph_data.pop('execution_status', {})
        self.return_values = graph_data.pop('return_values', {})
        self.project_root = graph_data.pop('project_root', self.project_root)

        # 恢复图结构
        self.graph = nx.node_link_graph(graph_data)

    def get_node_info(self, node: str) -> Dict[str, Any]:
        """获取节点信息"""
        if node not in self.graph:
            return {}

        return {
            'file': self.graph.nodes[node].get('file'),
            'lineno': self.graph.nodes[node].get('lineno'),
            'timestamp': self.graph.nodes[node].get('timestamp'),
            'module': self.graph.nodes[node].get('module'),
            'execution_time': self.execution_times.get(node),
            'return_value': self.return_values.get(node),
            'status': self.execution_status.get(node),
        }

    def get_module_graph(self, module_prefix: str = None) -> nx.DiGraph:
        """获取指定模块的子图"""
        if not module_prefix:
            return self.graph

        nodes = [
            n
            for n in self.graph.nodes
            if self.graph.nodes[n].get('module', '').startswith(module_prefix)
        ]
        return self.graph.subgraph(nodes)

    @staticmethod
    def code_similarity(original_code: str, modified_code: str) -> float:
        """计算代码相似度"""
        return SequenceMatcher(None, original_code, modified_code).ratio()

    def calculate_progress(self, current_node: str = None) -> Dict[str, float]:
        """计算执行进度，从多个维度分析"""
        progress = {}

        # 1. 基于节点完成度
        total_nodes = len(self.graph.nodes)
        completed_nodes = len(
            [n for n in self.graph.nodes if self.execution_status.get(n) == 'completed']
        )
        progress['node_completion'] = (
            (completed_nodes / total_nodes * 100) if total_nodes > 0 else 0
        )

        # 2. 基于关键路径
        if current_node:
            try:
                critical_path = self.get_critical_path()
                current_index = (
                    critical_path.index(current_node)
                    if current_node in critical_path
                    else -1
                )
                progress['critical_path'] = (
                    (current_index + 1) / len(critical_path) * 100
                    if current_index >= 0
                    else 0
                )
            except:
                progress['critical_path'] = 0

        # 3. 基于执行时间
        if self.execution_times:
            max_time = max(self.execution_times.values())
            current_time = time.time() - self.start_time
            progress['time_based'] = min(
                current_time / max_time * 100 if max_time > 0 else 0, 100
            )

        # 4. 基于模块完成度
        modules = self.get_module_completion()
        progress['module_completion'] = modules

        # 5. 计算总体进度（加权平均）
        weights = {
            'node_completion': 0.3,
            'critical_path': 0.3,
            'time_based': 0.2,
            'module_completion': 0.2,
        }

        progress['overall'] = sum(
            progress.get(k, 0) * w
            for k, w in weights.items()
            if k != 'module_completion'
        )

        return progress

    def get_critical_path(self) -> List[str]:
        """获取关键执行路径"""
        try:
            # 使用执行时间作为边的权重
            for edge in self.graph.edges():
                source, target = edge
                weight = self.execution_times.get(target, 0) - self.execution_times.get(
                    source, 0
                )
                self.graph.edges[edge]['weight'] = max(weight, 0)

            # 找到最长路径
            critical_path = nx.dag_longest_path(self.graph, weight='weight')
            return critical_path
        except:
            return []

    def get_module_completion(self) -> Dict[str, float]:
        """计算每个模块的完成度"""
        module_stats = {}

        for node in self.graph.nodes:
            module = self.graph.nodes[node].get('module', '').split('.')[0]
            if not module:
                continue

            if module not in module_stats:
                module_stats[module] = {'total': 0, 'completed': 0}

            module_stats[module]['total'] += 1
            if self.execution_status.get(node) == 'completed':
                module_stats[module]['completed'] += 1

        return {
            module: (stats['completed'] / stats['total'] * 100)
            for module, stats in module_stats.items()
            if stats['total'] > 0
        }

    def get_execution_bottlenecks(self) -> List[Dict[str, Any]]:
        """识别执行瓶颈"""
        bottlenecks = []

        # 计算平均执行时间
        avg_time = (
            sum(self.execution_times.values()) / len(self.execution_times)
            if self.execution_times
            else 0
        )

        for node in self.graph.nodes:
            exec_time = self.execution_times.get(node, 0)
            if exec_time > avg_time * 2:  # 执行时间超过平均值两倍
                bottlenecks.append(
                    {
                        'node': node,
                        'execution_time': exec_time,
                        'file': self.graph.nodes[node].get('file'),
                        'line': self.graph.nodes[node].get('lineno'),
                        'out_degree': self.graph.out_degree(node),  # 影响的下游节点数
                    }
                )

        return sorted(bottlenecks, key=lambda x: x['execution_time'], reverse=True)

    def get_execution_summary(self) -> Dict[str, Any]:
        """获取执行摘要"""
        return {
            'total_functions': len(self.graph.nodes),
            'completed_functions': len(
                [
                    n
                    for n in self.graph.nodes
                    if self.execution_status.get(n) == 'completed'
                ]
            ),
            'total_calls': len(self.graph.edges),
            'total_time': max(self.execution_times.values())
            if self.execution_times
            else 0,
            'modules': list(
                set(
                    self.graph.nodes[n].get('module', '').split('.')[0]
                    for n in self.graph.nodes
                )
            ),
            'bottlenecks': self.get_execution_bottlenecks()[:5],  # 前5个瓶颈
            'progress': self.calculate_progress(),
        }

    def visualize_progress(self, output_file: str):
        """可视化执行进度"""
        import matplotlib.pyplot as plt

        progress = self.calculate_progress()
        modules = progress['module_completion']

        # 创建条形图
        plt.figure(figsize=(12, 6))

        # 绘制总体进度
        plt.barh(0, progress['overall'], color='blue', alpha=0.6)
        plt.text(progress['overall'], 0, f"{progress['overall']:.1f}%", va='center')

        # 绘制模块进度
        y_pos = range(1, len(modules) + 1)
        module_values = list(modules.values())
        plt.barh(y_pos, module_values, color='green', alpha=0.6)

        # 添加标签
        plt.yticks([0] + list(y_pos), ['Overall'] + list(modules.keys()))
        plt.xlabel('Progress (%)')
        plt.title('Execution Progress')

        plt.tight_layout()
        plt.savefig(output_file)
        plt.close()
